Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ambient-loader proposal #63

Merged
merged 6 commits into from
Mar 27, 2022
Merged

Add ambient-loader proposal #63

merged 6 commits into from
Mar 27, 2022

Conversation

arcanis
Copy link
Contributor

@arcanis arcanis commented Feb 10, 2022

Rendered version

This design document is a follow-up to #58

Fixes #56

@GeoffreyBooth GeoffreyBooth added the loaders-agenda Issues and PRs to discuss during the meetings of the Loaders team label Feb 10, 2022
doc/design/proposal-ambient-loaders.md Outdated Show resolved Hide resolved

If loaders cannot generally influence each other, we could have a subset of them do. Indeed, not all loaders affect the resolution so much that they are a requirement to followup loaders. We could have two levels of loaders:

- Ambient loaders would be defined via the `--ambient-loader <module>` flag. They would be loaded sequentially, and would affect the resolution of all followup loaders.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I registered two ambient loaders, would the first one affect the resolution of the second?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

Copy link
Member

@JakobJingleheimer JakobJingleheimer Feb 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, wait, what. I did not get that from this doc. I understood ambient loaders to work in tandem like how regular loaders do (one regular loader does not affect the other), just specifically when loading a regular loader.

Suggested change
- Ambient loaders would be defined via the `--ambient-loader <module>` flag. They would be loaded sequentially, and would affect the resolution of all followup loaders.
- Ambient loaders would be defined via the `--ambient-loader <module>` flag. They would be loaded sequentially and would affect the resolution of all subsequent ambient loaders.

or possibly

Suggested change
- Ambient loaders would be defined via the `--ambient-loader <module>` flag. They would be loaded sequentially, and would affect the resolution of all followup loaders.
- Ambient loaders would be defined via the `--ambient-loader <module>` flag. They would be loaded sequentially and would affect the resolution of all subsequent ambient and regular loaders.

Making ambient loaders affect each other sounds like a pandora's box.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated with your second wording

Making ambient loaders affect each other sounds like a pandora's box.

Why that?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated with your second wording

Thanks!

Making ambient loaders affect each other sounds like a pandora's box.
Why that?

Wouldn't that open the door for reentry?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest I'm still not sure I see a problem with reentrance, we may have different behaviours in mind 😃

But in this case it shouldn't be reentrant anyway: it's just using the result of the previous loader to load the next one, so all execution occurs in the same order and we don't go twice in the same loader during the same resolution pass.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about when one ambient loader depends on another in a circular fashion?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have an example?

The closest I can think of would be if, say, we had /foo.zip/bar.tgz/qux.zip/index.js, but isn't this pattern true as well for regular module imports?

doc/design/proposal-ambient-loaders.md Show resolved Hide resolved
Copy link
Member

@JakobJingleheimer JakobJingleheimer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Proposal doc LGTM, thanks!

EDIT: Please update current line 19 (defining ambient loaders affects on each other) to specifically call out that they affect the resolution of other ambient loaders. (done in 706ea20)

arcanis and others added 2 commits February 15, 2022 18:23
Co-authored-by: Geoffrey Booth <456802+GeoffreyBooth@users.noreply.github.com>

Indeed, importing `ts-node` would require the `zip` loader to contribute to the resolution (so that the final path loaded is `$PROJECT/node_modules.zip/ts-node` instead of `$PROJECT/node_modules/ts-node`, which doesn't exist), but it’s not what happens today: both `zip` and `ts-node` are resolved by the default Node resolver, preventing `ts-node` from resolving.

The reason for that is an attempt to keep loaders as isolated as possible, to allow followup improvements like reusing them across workers or scaling them up. If all loaders were influenced by prior loaders, they’d effectively coalesce into a single one, which would prevent such work. Still, the problem remains.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we 100% certain that we cannot let regular loaders be influenced by prior loaders? I'm afraid that if ambient loaders seem more powerful than regular loaders, we will just end up with everyone always using --ambient-loader and forget about the other flag.

Copy link
Member

@JakobJingleheimer JakobJingleheimer Feb 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we 100% certain that we cannot let regular loaders be influenced by prior loaders?

I think we said no because it would require reentrancy, and our preliminary thoughts on this Ambient Loader concept was that it's the nuclear option so it would be chosen very infrequently (too easy to foot-gun, so only worth it when you really need it and really know what you're doing).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be curious to hear about reentrancy hazards that will crop up in the types of loaders that can be non-ambient. E.g. a use-case where you don't need --ambient-loader and yet are doing something tricky enough that you're likely to footgun. A simple source->source compiler such as a coffeescript loader wouldn't have any of these risks, I don't think.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@arcanis If you feel comfortable explaining it, do you mind adding a section to the document about reentrancy and its hazards and what this means for loaders/ambient loaders? Like perhaps how those issues can be addressed (or not) via ambient loaders, etc. Or what if we only allowed just one ambient loader?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I mentioned in this thread, I'm not sure myself I see the problems with reentrancy (nor I see it avoidable for resolution in general, or particularly affected by ambient loaders), so I'd prefer if someone else could make a writeup.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Who is concerned about these hypothetical reentrancy concerns? From an outside perspective, it appears as if perhaps it was hypothesized but no one could think of any examples.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Who is concerned about these hypothetical reentrancy concerns? From an outside perspective, it appears as if perhaps it was hypothesized but no one could think of any examples.

@bmeck was the one who knew the most about them.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also concerned about reentrancy, but for me that doesn't block adding the proposal itself, just a concern about a later stage.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw, I'm not blocking this proposal in any way. I do not have the bandwidth to participate in meetings, so I try to at least follow what's happening in this repo. I trust that you all are doing the right thing in the end 👍🏻

@JakobJingleheimer

This comment was marked as resolved.

@arcanis arcanis changed the title Adds the ambient-loader proposal Add ambient-loader proposal Feb 16, 2022
@guybedford
Copy link

@arcanis could the use cases be supported by permitting defining this property in the loader itself?

export const ambient = true;

@cspotcode
Copy link
Contributor

You'll be requiring node to load loaders sequentially, which I think is fine and is probably something we should do anyway, for all loaders.

node --loader a --loader b

If a is ambient, then b cannot be loaded until a is finished. And node will not know if a is ambient until importing it and checking the ambient export.

@GeoffreyBooth GeoffreyBooth removed the loaders-agenda Issues and PRs to discuss during the meetings of the Loaders team label Mar 13, 2022

```
node --ambient-loader zip
--ambient-loader pnp
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe there is some debate about the correct ordering of these, based on Slack discussion:

https://node-js.slack.com/archives/CEVD3CX33/p1647532211808939

The competing factors are a) which ordering is more intuitive, and b) if ordering should be done such that argv takes precedence over NODE_OPTIONS

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, thinking about this some more, maybe the ordering in this particular example doesn't matter.

Co-authored-by: Geoffrey Booth <456802+GeoffreyBooth@users.noreply.github.com>
@GeoffreyBooth
Copy link
Member

I think this is good to merge in, unless there are any objections? Merging doesn’t mean that it gets on the roadmap, just that it’s a proposal under consideration; and future PRs can further improve it.

@JakobJingleheimer
Copy link
Member

You'll be requiring node to load loaders sequentially, which I think is fine and is probably something we should do anyway, for all loaders.

node --loader a --loader b

If a is ambient, then b cannot be loaded until a is finished. And node will not know if a is ambient until importing it and checking the ambient export.

@cspotcode a bit of an implementation detail, but I think we could handle this in ESMLoader::pluck() (wherein a loader's exports are inspected and hooks etc are queued): we'd create a separate collection for ambient loaders.

@cspotcode
Copy link
Contributor

Oh interesting idea. If someone did export const ambient = false; then it would still trigger that logic I think? It's a weird case, but something that would have to be documented.

The flow would be:

  • Resolve and inspect a's exports via pluck()
  • If ambient export is present, then we know we cannot resolve b until a has evaluated, since a might affect the resolution of b

@JakobJingleheimer
Copy link
Member

Correct 🙂 (to clarify: that export const ambient = false would NOT cause it to be added to the ambient collection, only when true)

@JakobJingleheimer
Copy link
Member

JakobJingleheimer commented Mar 26, 2022

Gah, actually that wouldn't be so straightforward: a loader that needs an ambient loader would be unreadable, so the initial parse of the lay loader would fail before it gets to ESMLoader::pluck(). If we wanted to go the way of an export, we'd need to temporarily ignore failures and re-try until all loaders have been successfully parsed. I think this would get too ugly to be viable.

I think that doesn't block merging this proposal though (which doesn't depend on that).

@cspotcode
Copy link
Contributor

Agreed, that's what I was pointing out: b can't resolve until a is fully loaded. So it would only be a performance optimization with gotchas: as soon as you've proven that a is not ambient, you can proceed with resolving the next loader.

@cspotcode
Copy link
Contributor

And actually, export const ambient might rely on ambient loaders being listed strictly before (after?) non-ambient loaders in the command-line / NODE_OPTIONS args. Would get messy to do --loader ambient1 --loader b --loader ambient2; is ambient2 meant to affect b?

@GeoffreyBooth GeoffreyBooth merged commit e648658 into main Mar 27, 2022
@GeoffreyBooth GeoffreyBooth deleted the mael/ambient-loaders branch March 27, 2022 02:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Loader chaining *while loading loaders*
6 participants